import json
import os
import requests
import docker
import threading
import time
from datetime import datetime
from flask import current_app
from app.models.system import SystemLog
from app import socketio

class DockerImageUpdateService:
    def __init__(self):
        self.data_dir = current_app.config.get('DATA_DIR', '/tmp')
        self.config_file = os.path.join(self.data_dir, 'docker_images_config.json')
        self.remote_config_url = None  # 从配置文件读取
        self.docker_client = None
        self._init_docker_client()
        self._init_config()
        
        # 更新状态
        self.update_status = {
            'checking': False,
            'updating': False,
            'last_check': None,
            'last_update': None,
            'available_updates': [],
            'update_progress': 0,
            'update_message': '',
            'current_updating_image': None
        }

    def _init_docker_client(self):
        """初始化Docker客户端"""
        try:
            self.docker_client = docker.from_env()
            self.docker_client.ping()  # 测试连接
        except Exception as e:
            SystemLog.log("ERROR", "docker_update", f"Docker客户端初始化失败: {str(e)}")
            self.docker_client = None

    def _init_config(self):
        """初始化配置文件"""
        if not os.path.exists(self.config_file):
            # 创建默认配置
            default_config = {
                "remote_config_url": "https://example.com/docker-images-config.json",
                "images": [
                    {
                        "name": "mobox-docker/backend",
                        "registry": "crpi-axr6nxua9k92sqiz.cn-hangzhou.personal.cr.aliyuncs.com",
                        "current_version": "latest",
                        "auto_update": True,
                        "enabled": True
                    }
                ],
                "last_updated": None
            }
            self.save_config(default_config)

    def load_config(self):
        """加载本地配置"""
        try:
            with open(self.config_file, 'r', encoding='utf-8') as f:
                return json.load(f)
        except Exception as e:
            SystemLog.log("ERROR", "docker_update", f"加载配置失败: {str(e)}")
            return {"images": [], "remote_config_url": ""}

    def save_config(self, config):
        """保存配置到本地"""
        try:
            with open(self.config_file, 'w', encoding='utf-8') as f:
                json.dump(config, f, indent=2, ensure_ascii=False)
            return True
        except Exception as e:
            SystemLog.log("ERROR", "docker_update", f"保存配置失败: {str(e)}")
            return False

    def fetch_remote_config(self):
        """从远程获取最新的镜像配置"""
        config = self.load_config()
        remote_url = config.get('remote_config_url')
        
        if not remote_url:
            raise Exception("未配置远程配置URL")

        try:
            response = requests.get(remote_url, timeout=30)
            response.raise_for_status()
            return response.json()
        except Exception as e:
            SystemLog.log("ERROR", "docker_update", f"获取远程配置失败: {str(e)}")
            raise Exception(f"获取远程配置失败: {str(e)}")

    def compare_versions(self, local_config, remote_config):
        """比较本地和远程配置，找出可更新的镜像"""
        available_updates = []
        local_images = {img['name']: img for img in local_config.get('images', [])}
        remote_images = remote_config.get('images', [])

        for remote_image in remote_images:
            image_name = remote_image['name']
            remote_version = remote_image.get('latest_version', remote_image.get('current_version'))
            
            if image_name in local_images:
                local_image = local_images[image_name]
                local_version = local_image.get('current_version')
                
                # 如果版本不同且启用更新
                if (remote_version != local_version and 
                    local_image.get('enabled', True) and 
                    remote_image.get('available', True)):
                    
                    update_info = {
                        'name': image_name,
                        'registry': remote_image.get('registry', local_image.get('registry')),
                        'current_version': local_version,
                        'latest_version': remote_version,
                        'description': remote_image.get('description', ''),
                        'size': remote_image.get('size', 'unknown'),
                        'auto_update': local_image.get('auto_update', False)
                    }
                    available_updates.append(update_info)

        return available_updates

    def check_updates_async(self):
        """异步检查更新"""
        def check_updates_thread():
            try:
                self.update_status['checking'] = True
                self.update_status['available_updates'] = []
                socketio.emit('docker_update_status', self.update_status)
                
                SystemLog.log("INFO", "docker_update", "开始检查Docker镜像更新")
                
                # 获取本地配置
                local_config = self.load_config()
                
                # 获取远程配置
                remote_config = self.fetch_remote_config()
                
                # 比较版本
                updates = self.compare_versions(local_config, remote_config)
                
                self.update_status['available_updates'] = updates
                self.update_status['last_check'] = time.time()
                
                SystemLog.log("INFO", "docker_update", f"发现 {len(updates)} 个镜像可更新")
                
            except Exception as e:
                SystemLog.log("ERROR", "docker_update", f"检查更新失败: {str(e)}")
                self.update_status['available_updates'] = []
            finally:
                self.update_status['checking'] = False
                socketio.emit('docker_update_status', self.update_status)

        # 启动检查线程
        thread = threading.Thread(target=check_updates_thread)
        thread.daemon = True
        thread.start()

    def pull_image_async(self, image_info):
        """异步拉取镜像"""
        def pull_image_thread():
            try:
                self.update_status['updating'] = True
                self.update_status['update_progress'] = 0
                self.update_status['current_updating_image'] = image_info['name']
                self.update_status['update_message'] = '开始拉取镜像...'
                socketio.emit('docker_update_status', self.update_status)
                
                if not self.docker_client:
                    raise Exception("Docker客户端不可用")
                
                registry = image_info['registry']
                image_name = image_info['name']
                version = image_info['latest_version']
                full_image_name = f"{registry}/{image_name}:{version}"
                
                SystemLog.log("INFO", "docker_update", f"开始拉取镜像: {full_image_name}")
                
                # 拉取镜像
                self.update_status['update_message'] = f'正在拉取 {full_image_name}...'
                self.update_status['update_progress'] = 10
                socketio.emit('docker_update_status', self.update_status)
                
                # 使用docker客户端拉取镜像
                image = self.docker_client.images.pull(registry + "/" + image_name, tag=version)
                
                self.update_status['update_progress'] = 80
                self.update_status['update_message'] = '更新本地配置...'
                socketio.emit('docker_update_status', self.update_status)
                
                # 更新本地配置
                self._update_local_config(image_info)
                
                self.update_status['update_progress'] = 100
                self.update_status['update_message'] = f'镜像 {image_name} 更新完成'
                self.update_status['last_update'] = time.time()
                
                SystemLog.log("INFO", "docker_update", f"镜像更新完成: {full_image_name}")
                
                # 从可更新列表中移除已更新的镜像
                self.update_status['available_updates'] = [
                    update for update in self.update_status['available_updates'] 
                    if update['name'] != image_info['name']
                ]
                
            except Exception as e:
                self.update_status['update_message'] = f'更新失败: {str(e)}'
                SystemLog.log("ERROR", "docker_update", f"镜像更新失败: {str(e)}")
            finally:
                self.update_status['updating'] = False
                self.update_status['current_updating_image'] = None
                socketio.emit('docker_update_status', self.update_status)

        # 启动拉取线程
        thread = threading.Thread(target=pull_image_thread)
        thread.daemon = True
        thread.start()

    def _update_local_config(self, image_info):
        """更新本地配置中的镜像版本"""
        config = self.load_config()
        
        # 找到对应的镜像配置并更新版本
        for img in config.get('images', []):
            if img['name'] == image_info['name']:
                img['current_version'] = image_info['latest_version']
                img['last_updated'] = datetime.now().isoformat()
                break
        
        config['last_updated'] = datetime.now().isoformat()
        self.save_config(config)

    def get_local_images(self):
        """获取本地Docker镜像列表"""
        if not self.docker_client:
            return []
        
        try:
            images = self.docker_client.images.list()
            image_list = []
            
            for image in images:
                if not image.tags:  # 跳过没有标签的镜像
                    continue
                    
                for tag in image.tags:
                    if ':' in tag:
                        name, version = tag.rsplit(':', 1)
                    else:
                        name, version = tag, 'latest'
                    
                    image_list.append({
                        'id': image.id,
                        'name': name,
                        'version': version,
                        'size': image.attrs.get('Size', 0),
                        'created': image.attrs.get('Created', '')
                    })
            
            return image_list
        except Exception as e:
            SystemLog.log("ERROR", "docker_update", f"获取本地镜像失败: {str(e)}")
            return []

    def update_all_auto_images(self):
        """更新所有设置为自动更新的镜像"""
        auto_updates = [
            img for img in self.update_status['available_updates'] 
            if img.get('auto_update', False)
        ]
        
        if not auto_updates:
            return
        
        # 逐个更新自动更新的镜像
        for image_info in auto_updates:
            self.pull_image_async(image_info)
            # 等待上一个更新完成再开始下一个
            while self.update_status['updating']:
                time.sleep(2)

    def _get_registry_credentials(self):
        """从 config/update.json 或环境变量获取阿里云仓库用户名/密码"""
        username = os.getenv('ALIYUN_REGISTRY_USERNAME')
        password = os.getenv('ALIYUN_REGISTRY_PASSWORD')
        try:
            cfg_path = os.path.join(os.getcwd(), 'config', 'update.json')
            if os.path.exists(cfg_path):
                with open(cfg_path, 'r', encoding='utf-8') as f:
                    cfg = json.load(f)
                    reg_cfg = cfg.get('docker', {}).get('registry', {})
                    username = username or reg_cfg.get('username')
                    password = password or reg_cfg.get('password')
        except Exception as e:
            SystemLog.log("ERROR", "docker_update", f"读取registry凭证失败: {str(e)}")
        return username, password

    def _get_aliyun_repositories(self, registry, namespace):
        """从阿里云Container Registry获取仓库列表
        需要在环境变量中配置：
            ALIYUN_REGISTRY_USERNAME
            ALIYUN_REGISTRY_PASSWORD
        返回仓库名称列表（不包含命名空间前缀）
        """
        username, password = self._get_registry_credentials()
        catalog_url = f"https://{registry}/v2/_catalog"
        repos = []
        try:
            if username and password:
                resp = requests.get(catalog_url, auth=(username, password), timeout=15)
            else:
                # 匿名尝试
                resp = requests.get(catalog_url, timeout=15)
            resp.raise_for_status()
            data = resp.json()
            # data = {"repositories": ["namespace/repo1", "namespace/repo2", ...]}
            for repo in data.get('repositories', []):
                # 仅取指定命名空间下的仓库
                if repo.startswith(f"{namespace}/"):
                    repos.append(repo.split('/', 1)[1])
            return repos
        except Exception as e:
            SystemLog.log("ERROR", "docker_update", f"获取阿里云仓库列表失败: {str(e)}")
            return []

    def sync_aliyun_images(self):
        """同步阿里云镜像仓库的镜像列表到本地配置 (使用硬编码的实际存在镜像)"""
        try:
            registry = "crpi-axr6nxua9k92sqiz.cn-hangzhou.personal.cr.aliyuncs.com"
            namespace = "mobox-docker"

            # 硬编码的实际存在的镜像列表（包含所有版本）
            hardcoded_images = [
                {
                    "name": "mobox-docker/blocky",
                    "registry": registry,
                    "current_version": "latest",
                    "auto_update": True,
                    "enabled": True,
                    "description": "Blocky图形化编程服务"
                },
                {
                    "name": "mobox-docker/vue-ai-base", 
                    "registry": registry,
                    "current_version": "latest",
                    "auto_update": True,
                    "enabled": True,
                    "description": "Vue AI基础运行环境镜像"
                },
                {
                    "name": "mobox-docker/jupyter",
                    "registry": registry, 
                    "current_version": "jupyter",
                    "auto_update": True,
                    "enabled": True,
                    "description": "Jupyter在线编程环境"
                },
                {
                    "name": "mobox-docker/jupyter",
                    "registry": registry, 
                    "current_version": "gateway",
                    "auto_update": True,
                    "enabled": True,
                    "description": "Jupyter Gateway服务"
                },
                {
                    "name": "mobox-docker/jupyter",
                    "registry": registry, 
                    "current_version": "backend",
                    "auto_update": True,
                    "enabled": True,
                    "description": "Jupyter Backend服务"
                }
            ]

            config = self.load_config()
            # 只保留硬编码的3个镜像，清除其他所有镜像
            updated_images = hardcoded_images

            config['images'] = updated_images
            config['last_updated'] = datetime.now().isoformat()
            
            if self.save_config(config):
                SystemLog.log("INFO", "docker_update", f"重置为 {len(hardcoded_images)} 个核心镜像")
                return {
                    "success": True, 
                    "message": f"已重置为 {len(hardcoded_images)} 个核心镜像（blocky、vue-ai-base、jupyter:jupyter、jupyter:gateway、jupyter:backend）", 
                    "images_count": len(updated_images)
                }
            else:
                return {"success": False, "message": "保存配置失败"}
        except Exception as e:
            SystemLog.log("ERROR", "docker_update", f"同步阿里云镜像失败: {str(e)}")
            return {"success": False, "message": f"同步失败: {str(e)}"}
    
    def _get_image_description(self, image_name):
        """获取镜像描述"""
        descriptions = {
            "base": "基础运行环境镜像",
            "blockly": "Blockly图形化编程服务",
            "jupyter": "Jupyter在线编程环境",
            "dashboard": "系统管理仪表板",
            "gateway": "API网关和路由服务",
            "frontend": "Web前端界面服务",
            "backend": "API后端服务"
        }
        return descriptions.get(image_name, f"{image_name}服务镜像")

    def check_aliyun_image_updates(self):
        """检查阿里云镜像仓库中的更新"""
        from flask import current_app
        
        with current_app.app_context():
            try:
                self.update_status['checking'] = True
                self.update_status['update_message'] = '正在检查阿里云镜像更新...'
                socketio.emit('docker_update_status', self.update_status)
                
                # 先同步镜像列表
                sync_result = self.sync_aliyun_images()
                if not sync_result['success']:
                    self.update_status['checking'] = False
                    self.update_status['update_message'] = f'同步失败: {sync_result["message"]}'
                    socketio.emit('docker_update_status', self.update_status)
                    return
                
                # 检查远程配置更新
                result = self._check_remote_config_updates()
                
                self.update_status['checking'] = False
                self.update_status['last_check'] = time.time()
                
                if result:
                    self.update_status['update_message'] = f'发现 {len(result)} 个镜像更新'
                    self.update_status['available_updates'] = result
                else:
                    self.update_status['update_message'] = '所有镜像都是最新版本'
                    self.update_status['available_updates'] = []
                
                socketio.emit('docker_update_status', self.update_status)
                
            except Exception as e:
                self.update_status['checking'] = False
                self.update_status['update_message'] = f'检查更新失败: {str(e)}'
                socketio.emit('docker_update_status', self.update_status)
                SystemLog.log("ERROR", "docker_update", f"检查阿里云镜像更新失败: {str(e)}")

# 创建全局实例
docker_update_service = None

def get_docker_update_service():
    """获取Docker更新服务实例"""
    global docker_update_service
    if docker_update_service is None:
        docker_update_service = DockerImageUpdateService()
    return docker_update_service 